/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#define LOG_TAG "GnssHAL_GnssIpcp"

#include "GnssIpcp.h"

#include <Positioning.h>
#include <android-base/strings.h>
#include <cutils/log.h>
#include <errno.h>
#include <fcntl.h>
#include <log/log.h>
#include <math.h>
#include <pthread.h>
#include <sys/epoll.h>
#include <time.h>
#include <utils/SystemClock.h>

#include "GnssPositioning.h"
#include "android-base/parsedouble.h"
#include "android-base/parseint.h"

namespace vendor {
namespace ecarx {
namespace xma {
namespace gnss {
namespace V1_0 {
namespace implementation {

#define LOCAL_IP "198.18.34.15"

#define GPS_DEBUG 1

#if GPS_DEBUG
#define D(...) ALOGD(__VA_ARGS__)
#else
#define D(...) ((void)0)
#endif

using ::vendor::ecarx::xma::iplm::V1_0::IPLM_Preconfigured_Service;
using ::vendor::ecarx::xma::iplm::V1_0::ServiceStatus;
using ::vendor::ecarx::xma::iplm::V1_0::StatusCode;

std::mutex GnssIpcp::sMutex;

uint16_t Positioning_OperationIDs[2] = {ASN1V_poiInfoPushOpID, ASN1V_gnssRawInfoOpID};

GpsInterface GnssIpcp::sGpsInterface = {
    .size = sizeof(GpsInterface),
    .init = initGps,
    .start = startGps,
    .stop = stopGps,
    .cleanup = cleanupGps,
    .inject_time = injectTimeGps,
    .inject_location = injectLocationGps,
    .delete_aiding_data = deleteAidingDataGps,
    .set_position_mode = setPositionModeGps,
    .get_extension = getExtensionGps,
};

ipcp_callbacks_t GnssIpcp::sIpcpCallbacks = {
    .ipcp_received_cb = ipcpReceivedCallback,
    .error_cb = ipcpStackErrorCallback,
};

sp<IIplm> GnssIpcp::mIplm;
std::thread GnssIpcp::mIplmThread;
std::thread GnssIpcp::mIpcpThread;
sp<IplmRecipient> GnssIpcp::sIplmRecipient = new IplmRecipient();

#define GSA_INFO_PRN_CNT 12
static int sGP_PRNList[GSA_INFO_PRN_CNT];
static int sGL_PRNList[GSA_INFO_PRN_CNT];
static int sGA_PRNList[GSA_INFO_PRN_CNT];
static int sBD_PRNList[GSA_INFO_PRN_CNT];
static bool sGSVReceiveComplete = false;
typedef struct {
    int utc_year;
    int utc_mon;
    int utc_day;
    int utc_diff;
    GnssLocation fix;
    gnssLocationCb gnss_location_cb;
} NmeaReader;

static NmeaReader sNmeaReader[1];

/* commands sent to the gps thread */
enum { CMD_QUIT = 0, CMD_START = 1, CMD_STOP = 2 };

/*
 * This enum is used by gpsSvStatusCb() method below to convert GpsSvStatus
 * to GnssSvStatus for backward compatibility. It is only used by the default
 * implementation and is not part of the GNSS interface.
 */
enum SvidValues : uint16_t {
    GLONASS_SVID_OFFSET = 64,
    GLONASS_SVID_COUNT = 24,
    BEIDOU_SVID_OFFSET = 200,
    BEIDOU_SVID_COUNT = 35,
    SBAS_SVID_MIN = 33,
    SBAS_SVID_MAX = 64,
    SBAS_SVID_ADD = 87,
    QZSS_SVID_MIN = 193,
    QZSS_SVID_MAX = 200,
    GALILEO_SVID_OFFSET = 300,
    GALILEOBEIDOU_SVID_COUNT = 36
};

/* this is the state of our connection to the qemu_gpsd daemon */
typedef struct {
    int init;
    int initIpcp;
    GpsCallbacks callbacks;
    pthread_t thread;
    int control[2];
} GpsState;

static GpsState sGpsState[1];

static GnssSvStatus sGnssSvStatus;
static int sCurrentSvNumber;
static int64_t sCurrentReceiveTime;
static int gnssRunStarted = 0;

static int epoll_register(int epoll_fd, int fd) {
    struct epoll_event ev;
    int ret, flags;

    /* important: make the fd non-blocking */
    flags = fcntl(fd, F_GETFL);
    if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) != 0) {
        ALOGE("epoll_register failed to fcntl(F_SETFL)");
    }

    ev.events = EPOLLIN;
    ev.data.fd = fd;
    do {
        ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev);
    } while (ret < 0 && errno == EINTR);
    return ret;
}

static int epoll_deregister(int epoll_fd, int fd) {
    int ret;
    do {
        ret = epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, NULL);
    } while (ret < 0 && errno == EINTR);
    return ret;
}

int GnssIpcp::initGnssIpcp() {
    ALOGI("GnssIpcp initialization ...");
    memset(sGpsState, 0, sizeof(*sGpsState));
    memset(&sGnssSvStatus, 0, sizeof(sGnssSvStatus));
    memset(sGP_PRNList, 0, sizeof(sGP_PRNList));
    memset(sGL_PRNList, 0, sizeof(sGL_PRNList));
    memset(sGA_PRNList, 0, sizeof(sGA_PRNList));
    memset(sBD_PRNList, 0, sizeof(sBD_PRNList));
    sGSVReceiveComplete = false;
    sGnssSvStatus.size = sizeof(sGnssSvStatus);
    sGnssSvStatus.num_svs = 0;
    sCurrentReceiveTime = android::elapsedRealtime();

    nmeaInit();

    return setupIpcp();
}

void GnssIpcp::releaseGnssIpcp() {
    ALOGI("GnssIpcp releaseGnssIpcp ...");
    if (GnssIpcp::mIplmThread.joinable()) {
        ALOGI("start call mIplmThread.join()");
        GnssIpcp::mIplmThread.join();
    }

    if (GnssIpcp::mIpcpThread.joinable()) {
        ALOGI("setupIpcp start call mIpcpThread.join()");
        GnssIpcp::mIpcpThread.join();
    }

    GpsState* state = sGpsState;
    if (state->initIpcp) {
        GnssIpcp::cleanupIpcp();
        state->initIpcp = 0;
    }
}

const GpsInterface* GnssIpcp::getGpsInterface() {
    return &sGpsInterface;
}

void IplmRecipient::serviceDied(uint64_t cookie, const wp<::android::hidl::base::V1_0::IBase>& who) {
    ALOGW("IIplm service died!");
    GnssIpcp::mIplm->unlinkToDeath(GnssIpcp::sIplmRecipient);
    GnssIpcp::mIplm = nullptr;

    GnssIpcp::reportStatusToIplm();
}

void GnssIpcp::reportStatusToIplm() {
    if (GnssIpcp::mIplmThread.joinable()) {
        ALOGI("reportStatusToIplm start call mIplmThread.join()");
        GnssIpcp::mIplmThread.join();
    }

    GnssIpcp::mIplmThread = std::thread([]() {
        while (GnssIpcp::mIplm == nullptr) {
            ALOGI("get IPLM servvice...");
            GnssIpcp::mIplm = IIplm::getService("default");

            if (GnssIpcp::mIplm == nullptr) {
                ALOGE("Read IIplm service failed!");
                std::this_thread::sleep_for(std::chrono::milliseconds(1000));
            } else {
                GnssIpcp::mIplm->linkToDeath(GnssIpcp::sIplmRecipient, 0);

                if (GnssIpcp::mIplm->setPrecfgServiceStatus(IPLM_Preconfigured_Service::POSITIONING,
                                                            ServiceStatus::OnLine) ==
                    StatusCode::SUCCESS) {
                    ALOGI("IPLM setServiceStart(POSITIONING) succeed.");
                } else {
                    ALOGE("IPLM setServiceStart(POSITIONING) failed!");
                }
                break;
            }
        }
    });

    GnssIpcp::mIplmThread.detach();
}

/**
 * Opens the interface and provides the callback routines
 * to the implementation of this interface.
 */
int GnssIpcp::initGps(GpsCallbacks* callbacks) {
    GpsState* state = sGpsState;

    ALOGD("%s: called", __FUNCTION__);

    if (!state->init) {
        state->control[0] = -1;
        state->control[1] = -1;

        if (socketpair(AF_LOCAL, SOCK_STREAM, 0, state->control) < 0) {
            ALOGE("could not create thread control socket pair: %s", strerror(errno));
            cleanupGps();
        } else {
            state->thread = callbacks->create_thread_cb("gps_state_thread", gpsStateThread, state);
        }

        if (!state->thread) {
            ALOGE("could not create gps thread: %s", strerror(errno));
            cleanupGps();
        } else {
            state->callbacks = *callbacks;

            // Explicitly initialize capabilities
            state->callbacks.set_capabilities_cb(0);

            // Setup system info, we are pre 2020 hardware.
            GnssSystemInfo sysinfo;
            sysinfo.size = sizeof(GnssSystemInfo);
            sysinfo.year_of_hw = 2019;
            state->callbacks.set_system_info_cb(&sysinfo);

            state->init = 1;
            D("gps state initialized");
        }
    }

    return (state->init == 1 ? 0 : -1);
}

/** Starts navigating. */
int GnssIpcp::startGps(void) {
    int ret;
    char cmd = CMD_START;
    GpsState* s = sGpsState;

    ALOGD("%s: called", __FUNCTION__);

    if (!s->init) {
        ALOGD("%s: called with uninitialized state !!", __FUNCTION__);
        return -1;
    }

    do {
        ret = write(s->control[0], &cmd, 1);
    } while (ret < 0 && errno == EINTR);

    if (ret != 1) {
        D("%s: could not send CMD_START command: ret=%d: %s", __FUNCTION__, ret, strerror(errno));
    }
    return 0;
}

/** Stops navigating. */
int GnssIpcp::stopGps(void) {
    int ret;
    char cmd = CMD_STOP;
    GpsState* s = sGpsState;

    ALOGD("%s: called", __FUNCTION__);

    if (!s->init) {
        ALOGD("%s: called with uninitialized state !!", __FUNCTION__);
        return -1;
    }

    do {
        ret = write(s->control[0], &cmd, 1);
    } while (ret < 0 && errno == EINTR);

    if (ret != 1) {
        D("%s: could not send CMD_STOP command: ret=%d: %s", __FUNCTION__, ret, strerror(errno));
    }
    return 0;
}

/** Closes the interface. */
void GnssIpcp::cleanupGps(void) {
    GpsState* s = sGpsState;

    ALOGD("%s: called", __FUNCTION__);

    if (s->init) {
        // tell the thread to quit, and wait for it
        char cmd = CMD_QUIT;
        void* dummy;
        write(s->control[0], &cmd, 1);
        if (s->thread) {
            pthread_join(s->thread, &dummy);
            s->thread = 0;
        }

        // close the control socket pair
        close(s->control[0]);
        s->control[0] = -1;
        close(s->control[1]);
        s->control[1] = -1;

        s->init = 0;
    }
}

/** Injects the current time. */
int GnssIpcp::injectTimeGps(GpsUtcTime time, int64_t timeReference, int uncertainty) {
    return 0;
}

/**
 * Injects current location from another location provider (typically cell
 * ID). Latitude and longitude are measured in degrees expected accuracy is
 * measured in meters
 */
int GnssIpcp::injectLocationGps(double latitude, double longitude, float accuracy) {
    return 0;
}

/**
 * Specifies that the next call to start will not use the
 * information defined in the flags. GPS_DELETE_ALL is passed for
 * a cold start.
 */
void GnssIpcp::deleteAidingDataGps(GpsAidingData flags) {}

/**
 * min_interval represents the time between fixes in milliseconds.
 * preferred_accuracy represents the requested fix accuracy in meters.
 * preferred_time represents the requested time to first fix in milliseconds.
 *
 * 'mode' parameter should be one of GPS_POSITION_MODE_MS_BASED
 * or GPS_POSITION_MODE_STANDALONE.
 * It is allowed by the platform (and it is recommended) to fallback to
 * GPS_POSITION_MODE_MS_BASED if GPS_POSITION_MODE_MS_ASSISTED is passed in, and
 * GPS_POSITION_MODE_MS_BASED is supported.
 */
int GnssIpcp::setPositionModeGps(GpsPositionMode mode, GpsPositionRecurrence recurrence,
                                 uint32_t min_interval, uint32_t preferred_accuracy,
                                 uint32_t preferred_time) {
    // FIXME - support fix_frequency
    return 0;
}

/** Get a pointer to extension information. */
const void* GnssIpcp::getExtensionGps(const char* name) {
    // no extensions supported
    return NULL;
}

int GnssIpcp::setupIpcp() {
    ALOGD("%s: called", __FUNCTION__);

    if (GnssIpcp::mIpcpThread.joinable()) {
        ALOGI("setupIpcp start call mIpcpThread.join()");
        GnssIpcp::mIpcpThread.join();
    }

    GnssIpcp::mIpcpThread = std::thread([]() {
        GpsState* state = sGpsState;
        ipcp_transport_conf_t config;
        ipcp_stack_err_t error_code;

        memset(&config, 0, sizeof(config));
        config.service_id = ASN1V_positioningServiceID;
        strcpy(config.host_ip, LOCAL_IP);
        config.host_port = ASN1V_positioningPort;
        config.sk_type = IPCP_TRANSPORT_UDP;
        config.vlan = 5;
        config.priority = 5;
        set_log_tag(LOG_TAG);

        while (!state->initIpcp) {
            error_code = ipcp_setup_with_opids(config, &GnssIpcp::sIpcpCallbacks,
                                               Positioning_OperationIDs, 2);
            ALOGI("setupIpcp error_code = %d", error_code);

            if (error_code == STACK_START_SUCCESS) {
                state->initIpcp = 1;
                D("setup ipcp succeed.");

                reportStatusToIplm();
                break;
            } else {
                ALOGE("setup ipcp failed!");
                std::this_thread::sleep_for(std::chrono::milliseconds(2000));
            }
        }
    });

    GnssIpcp::mIpcpThread.detach();
    return 1;
}

void GnssIpcp::cleanupIpcp() {
    ALOGD("%s: called", __FUNCTION__);

    ipcp_cleanup();
}

/**
 * When STACK receives following IPCP packets, STACK would call this callback to notify caller.
 *      #REQUEST
 *      #RESPONSE
 *      #SETREQUEST
 *      #SETQUEST_NORETURN
 *      #ERROR
 *      #NOTIFICATION
 *      #NOTIFICATION_CYCLIC.
 *
 *  NOTE: Upper layer *MUST NOT* do any block operation in callback function.
 *        Upper layer *MUST memcpy this ipcp_packet*, because IPCP Stack would free
 *        the *ipcp_packet after this cb is called.
 */
void GnssIpcp::ipcpReceivedCallback(ipcp_packet_t* ipcp_packet, ipcp_peer_t peer) {
    const int MAXREADLEN = 1024;
    static char printBuffer[MAXREADLEN];
    ipcp_header_t* header = &ipcp_packet->header;
    uint16_t payloadLen = header->length - 8;

    memset(printBuffer, 0, MAXREADLEN);

    if (header->operation_id == ASN1V_poiInfoPushOpID) {
        // OperationID: POIInfoPush (0x0001)
        switch (header->op_type) {
            case IPCP_OPERATION_NOTIFICATION: {
                ASN1PERDecodeBuffer decodeBuffer((OSOCTET*)ipcp_packet->payload, payloadLen, false);
                ASN1T_OpPOIInfoPush_Notification data;
                ASN1C_OpPOIInfoPush_Notification OpPOIInfoPush_NotificationPDU(decodeBuffer, data);

                if (OpPOIInfoPush_NotificationPDU.Decode()) {
                    ALOGE("decode of OpPOIInfoPush_Notification failed!");
                } else {
                    OpPOIInfoPush_NotificationPDU.toString(printBuffer, MAXREADLEN);
                    ALOGI("ipcpReceivedCallback :%s", printBuffer);

                    ASN1T_OpPOIInfoPush_Notification& in = OpPOIInfoPush_NotificationPDU.getData();
                    ASN1T_PostAddress address = in.postAddress;
                    GnssPositioning::onPOIInfoNotification(
                        (const char*)in.poiname, in.longitude, in.latitude,
                        (const char*)address.street, (const char*)address.houseNumber,
                        (const char*)address.city, (const char*)address.region,
                        (const char*)address.postcode, (const char*)address.country);
                }
                break;
            }
            default:
                ALOGI("ipcpReceivedCallback unknown packet sID: %hu, OpID: %hu, OpType: %hhu",
                      header->service_id, header->operation_id, header->op_type);
                break;
        }
    } else if (header->operation_id == ASN1V_gnssRawInfoOpID) {
        // OperationID: GNSSRawInfo (0x0006)
        switch (header->op_type) {
            case IPCP_OPERATION_NOTIFICATION:
            case IPCP_OPERATION_NOTIFICATION_CYCLIC: {
                ASN1PERDecodeBuffer decodeBuffer((OSOCTET*)ipcp_packet->payload, payloadLen, false);
                ASN1T_OpGNSSRawInfo_Notification data;
                ASN1C_OpGNSSRawInfo_Notification OpGNSSRawInfo_NotificationPDU(decodeBuffer, data);

                if (OpGNSSRawInfo_NotificationPDU.Decode()) {
                    ALOGE("decode of OpGNSSRawInfo_Notification failed!");
                } else {
                    OpGNSSRawInfo_NotificationPDU.toString(printBuffer, MAXREADLEN);
                    ALOGV("ipcpReceivedCallback :%s", printBuffer);

                    ASN1T__SeqOfNMEA0183* pvalue =
                        &OpGNSSRawInfo_NotificationPDU.getData().nMEA0183List;
                    OSRTDListNode* pnode;
                    ASN1T_NMEA0183* pdata;

                    // call NMEA0813 analysis
                    for (pnode = pvalue->head; pnode != 0; pnode = pnode->next) {
                        pdata = ((ASN1T_NMEA0183*)pnode->data);

                        std::string nmeaValue((const char*)pdata->nMEA0183Data.data,
                                              pdata->nMEA0183Data.numocts);

                        nmeaParse(nmeaValue, (pvalue->tail == pnode));
                    }
                }
                break;
            }
            default:
                ALOGI("ipcpReceivedCallback unknown packet sID: %hu, OpID: %hu, OpType: %hhu",
                      header->service_id, header->operation_id, header->op_type);
                break;
        }
    }
}

/**
 * When WFA/WFR Timeout occurres or some STACK error occurred,
 * STACK would notify caller via this function pointer.
 */
void GnssIpcp::ipcpStackErrorCallback(ipcp_packet_t* ipcp_packet, ipcp_stack_err_t error) {
    ALOGI("ipcpStackErrorCallback error = %d", error);
    switch (error) {
        case STACK_FAILURE_CREATE_SOCKET:
        case STACK_FAILURE_SET_VLAN:
        case STACK_FAILURE_BIND_SOCKET:
        case STACK_FAILURE_LISTEN_SOCKET:
        case STACK_FAILURE_ACCEPT_SOCKET:
        case STACK_FAILURE_CONNECT_SOCKET:
        case STACK_FAILURE_CREATE_THREAD:
            cleanupIpcp();
            sleep(2);
            setupIpcp();
            break;
        case STACK_FAILURE_WFA_TIMEOUT:
            ALOGE("ipcpStackErrorCallback STACK_FAILURE_WFA_TIMEOUT");
            break;
        case STACK_FAILURE_WFR_TIMEOUT:
            ALOGE("ipcpStackErrorCallback STACK_FAILURE_WFR_TIMEOUT");
            break;
        default:
            ALOGE("ipcpStackErrorCallback unknown!");
            break;
    }
}

/** this is the main thread, it waits for commands from gps_state_start/stop and,
 * when started, messages from the QEMU GPS daemon. these are simple NMEA sentences
 * that must be parsed to be converted into GPS fixes sent to the framework
 */
void GnssIpcp::gpsStateThread(void* arg) {
    GpsState* state = (GpsState*)arg;
    int epoll_fd = epoll_create(2);
    int control_fd = state->control[1];
    GpsStatus gps_status;
    memset(&gps_status, 0, sizeof(gps_status));
    gps_status.size = sizeof(gps_status);

    // nmeaInit();

    // register control file descriptors for polling
    epoll_register(epoll_fd, control_fd);

    D("gps thread running");

    // now loop
    while (1) {
        struct epoll_event events[2];
        int ne, nevents;

        int timeout = -1;
        if (gps_status.status == GPS_STATUS_SESSION_BEGIN) {
            timeout = 10 * 1000;  // 10 seconds
        }
        nevents = epoll_wait(epoll_fd, events, 2, timeout);
        if (state->callbacks.sv_status_cb &&
            (state->initIpcp == 0 ||
             (android::elapsedRealtime() - sCurrentReceiveTime) > 10 * 1000)) {
            memset(&sGnssSvStatus, 0, sizeof(sGnssSvStatus));
            sGnssSvStatus.size = sizeof(sGnssSvStatus);
            sGnssSvStatus.num_svs = 0;
            state->callbacks.gnss_sv_status_cb(&sGnssSvStatus);
        }
        // update satilite info
        if (nevents < 0) {
            if (errno != EINTR) {
                ALOGE("epoll_wait() unexpected error: %s", strerror(errno));
                continue;
            }
        }
        D("gps thread received %d events", nevents);
        for (ne = 0; ne < nevents; ne++) {
            if ((events[ne].events & (EPOLLERR | EPOLLHUP)) != 0) {
                ALOGE("EPOLLERR or EPOLLHUP after epoll_wait() !?");
                return;
            }
            if ((events[ne].events & EPOLLIN) != 0) {
                int fd = events[ne].data.fd;

                if (fd == control_fd) {
                    char cmd = 255;
                    int ret;
                    D("gps control fd event");
                    do {
                        ret = read(fd, &cmd, 1);
                    } while (ret < 0 && errno == EINTR);

                    if (cmd == CMD_QUIT) {
                        D("gps thread quitting on demand");
                        return;
                    } else if (cmd == CMD_START) {
                        if (!gnssRunStarted) {
                            D("gps thread starting  gnss_location_cb=%p", sNmeaReader->gnss_location_cb);
                            gnssRunStarted = 1;
                            // nmeaSetCallback(sNmeaReader->gnss_location_cb);
                            // Cancel nmeaSetCallback, Add Gnss.cpp setCallback func add GnssIpcp::nmeaSetCallback invoke
                            gps_status.status = GPS_STATUS_SESSION_BEGIN;
                            if (state->callbacks.status_cb) {
                                state->callbacks.status_cb(&gps_status);
                            }
                        }
                    } else if (cmd == CMD_STOP) {
                        if (gnssRunStarted) {
                            D("gps thread stopping");
                            gnssRunStarted = 0;
                            // nmeaSetCallback(NULL);
                            gps_status.status = GPS_STATUS_SESSION_END;
                            if (state->callbacks.status_cb) {
                                state->callbacks.status_cb(&gps_status);
                            }
                        }
                    }
                } else {
                    ALOGE("epoll_wait() returned unkown fd %d ?", fd);
                }
            }
        }
    }

    // deregister control file descriptors for polling
    epoll_deregister(epoll_fd, control_fd);
}

void GnssIpcp::nmeaInit() {
    memset(sNmeaReader, 0, sizeof(*sNmeaReader));

    sNmeaReader->utc_year = -1;
    sNmeaReader->utc_mon = -1;
    sNmeaReader->utc_day = -1;
    sNmeaReader->gnss_location_cb = NULL;

    nmeaUpdateUtcDiff();
}

void GnssIpcp::nmeaSetCallback(gnssLocationCb cb) {
    sMutex.lock();
    sNmeaReader->gnss_location_cb = cb;
    if (cb != NULL && sNmeaReader->fix.gnssLocationFlags != 0) {
        D("%s: sending latest fix to new callback", __FUNCTION__);
        sNmeaReader->gnss_location_cb(&sNmeaReader->fix);
    }
    sMutex.unlock();
}

void GnssIpcp::nmeaParse(std::string nmeaValue, bool isLastEntry) {
    if (nmeaValue.length() < 9) {
        D("Too short. discarded. (%s)", nmeaValue.c_str());
        return;
    }

    std::vector<std::string> parts = android::base::Split(nmeaValue, ",");
    std::string gnssSystemID = (parts[0].size() > 3 ? parts[0].substr(1, 2) : "");
    std::string frameKey = (parts[0].size() > 3 ? parts[0].substr(3, parts[0].length() - 3) : "");

    D("gnssSystemID = %s, frameKey = %s, parts.size() = %lu [%s]", gnssSystemID.c_str(),
      frameKey.c_str(), parts.size(), nmeaValue.c_str());

    if (frameKey.size() > 1 && sGpsState->callbacks.nmea_cb) {
        struct timeval tv;
        gettimeofday(&tv, (struct timezone*)NULL);
        int64_t now = tv.tv_sec * 1000LL + tv.tv_usec / 1000;
        sGpsState->callbacks.nmea_cb(now, nmeaValue.c_str(), nmeaValue.length());
    }

    // NMEA 0813 V4.10
    if (frameKey == "GGA" && parts.size() == 15) {
        // check quality [0:No Fix / Invalid, 1:Standard GPS (2D/3D), 2:Differential GPS,
        // 6:Estimated (DR) Fix]
        if (parts[6] != "0") {
            nmeaUpdateTime(parts[1]);
            nmeaUpdateLatlong(parts[2], parts[3], parts[4], parts[5]);
            nmeaUpdateAltitude(parts[9], parts[10]);
            //nmeaUpdateAccuracy(parts[8]);
        }
    } else if (frameKey == "GSA" && parts.size() == 19) {
        int systemID = 0;
        // NMEA defined GNSS System ID 1:GSP, 2:GLONASS, 3:Galileo, 4:Beidou, 5-F:RESERVED
        std::vector<std::string> gsaSystemID = android::base::Split(parts[18], "*");
        android::base::ParseInt(gsaSystemID[0].c_str(), &systemID);

        if ((systemID == 1 && gnssSystemID == "GN") || gnssSystemID == "GP") {
            // GPS
            memset(sGP_PRNList, 0, sizeof(sGP_PRNList));
            for (int i = 0; i < GSA_INFO_PRN_CNT; i++) {
                android::base::ParseInt(parts[3 + i], &sGP_PRNList[i]);
            }
        } else if ((systemID == 2 && gnssSystemID == "GN") || gnssSystemID == "GL") {
            // GLONASS
            memset(sGL_PRNList, 0, sizeof(sGL_PRNList));
            for (int i = 0; i < GSA_INFO_PRN_CNT; i++) {
                android::base::ParseInt(parts[3 + i], &sGL_PRNList[i]);
            }
        } else if ((systemID == 3 && gnssSystemID == "GN") || gnssSystemID == "GA") {
            // Galileo
            memset(sGA_PRNList, 0, sizeof(sGA_PRNList));
            for (int i = 0; i < GSA_INFO_PRN_CNT; i++) {
                android::base::ParseInt(parts[3 + i], &sGA_PRNList[i]);
            }
        } else if ((systemID == 4 && gnssSystemID == "GN") ||
                   (gnssSystemID == "BD" || gnssSystemID == "GB")) {
            // Beidou
            memset(sBD_PRNList, 0, sizeof(sBD_PRNList));
            for (int i = 0; i < GSA_INFO_PRN_CNT; i++) {
                android::base::ParseInt(parts[3 + i], &sBD_PRNList[i]);
                if (sBD_PRNList[i] > BEIDOU_SVID_OFFSET && gnssSystemID == "GN") {
                    sBD_PRNList[i] -= BEIDOU_SVID_OFFSET;
                }
            }
        } else {
            ALOGW("RESERVED GSA System ID %d.", systemID);
        }

        // navMode 1:Fix  not available, 2:2D Fix, 3:3D Fix
        if (parts[2] != "1") {
            nmeaUpdateHorizontalAccuracy(parts[16]);
            nmeaUpdateVerticalAccuracy(parts[17]);
        }
    } else if (frameKey == "VTG" && parts.size() == 10) {
        // nmeaUpdateBearing(parts[1]);
        // nmeaUpdateSpeed(parts[5]);
    } else if (frameKey == "RMC" && parts.size() == 14) {
        if (parts[2] == "A") {
            nmeaUpdateDate(parts[9], parts[1]);
            nmeaUpdateLatlong(parts[3], parts[4], parts[5], parts[6]);
            nmeaUpdateSpeed(parts[7]);
            nmeaUpdateBearing(parts[8]);
        }
    } else if (frameKey == "GSV" && ((parts.size() - 1) % 4) == 0) {
        int sentenceSvCount = (parts.size() - 4 - 1) / 4;
        int sentenceCount = 0, sentenceNumber = 0, svCount = 0;
        android::base::ParseInt(parts[1], &sentenceCount);
        android::base::ParseInt(parts[2], &sentenceNumber);
        android::base::ParseInt(parts[3], &svCount);

        sCurrentReceiveTime = android::elapsedRealtime();
        sGSVReceiveComplete = (sentenceCount == sentenceNumber);

        // GnssStatus -> public int getSvid (int satelliteIndex)
        // GPS:  1-32
        // SBAS: 120-151, 183-192
        // GLONASS: One of: OSN, or FCN+100
        //     1-24 as the orbital slot number (OSN) (preferred, if known)
        //     93-106 as the frequency channel number (FCN) (-7 to +6) plus 100. i.e. encode FCN of
        //     -7 as 93, 0 as 100, and +6 as 106
        // QZSS: 193-200
        // Galileo: 1-36
        // Beidou:  1-37
        // IRNSS:   1-14
        for (int i = 0; i < sentenceSvCount && sCurrentSvNumber < GNSS_MAX_SVS; i++) {
            int sentenceOffset = i * 4;
            double dblValue = 0.0;
            GnssSvInfo* gnssSvInfo = &sGnssSvStatus.gnss_sv_list[sCurrentSvNumber];
            gnssSvInfo->size = sizeof(gnssSvInfo);

            // Pseudo-random number for the SV
            android::base::ParseInt(parts[sentenceOffset + 4].c_str(), &gnssSvInfo->svid);

            gnssSvInfo->flags = GNSS_SV_FLAGS_HAS_EPHEMERIS_DATA;
            for (int prnSn = 0; prnSn < GSA_INFO_PRN_CNT; prnSn++) {
                if (gnssSystemID == "GP") {
                    if (gnssSvInfo->svid == sGP_PRNList[prnSn]) {
                        gnssSvInfo->flags |= GNSS_SV_FLAGS_USED_IN_FIX;
                        break;
                    }
                } else if (gnssSystemID == "GL") {
                    if (gnssSvInfo->svid == sGL_PRNList[prnSn]) {
                        gnssSvInfo->flags |= GNSS_SV_FLAGS_USED_IN_FIX;
                        break;
                    }
                } else if (gnssSystemID == "GA") {
                    if (gnssSvInfo->svid == sGA_PRNList[prnSn]) {
                        gnssSvInfo->flags |= GNSS_SV_FLAGS_USED_IN_FIX;
                        break;
                    }
                } else if (gnssSystemID == "BD" || gnssSystemID == "GB") {
                    if (gnssSvInfo->svid == sBD_PRNList[prnSn]) {
                        gnssSvInfo->flags |= GNSS_SV_FLAGS_USED_IN_FIX;
                        break;
                    }
                }
            }

            if (gnssSystemID == "GP" || gnssSystemID == "GN") {
                // GNGSV statements should not normally exist, this is to deal with abnormal conditions.
                if (gnssSvInfo->svid >= 1 && gnssSvInfo->svid <= 32) {
                    gnssSvInfo->constellation = GNSS_CONSTELLATION_GPS;
                } else if (gnssSvInfo->svid > GLONASS_SVID_OFFSET &&
                           gnssSvInfo->svid <= GLONASS_SVID_OFFSET + GLONASS_SVID_COUNT) {
                    gnssSvInfo->constellation = GNSS_CONSTELLATION_GLONASS;
                    gnssSvInfo->svid -= GLONASS_SVID_OFFSET;
                } else if (gnssSvInfo->svid > BEIDOU_SVID_OFFSET &&
                           gnssSvInfo->svid <= BEIDOU_SVID_OFFSET + BEIDOU_SVID_COUNT) {
                    gnssSvInfo->constellation = GNSS_CONSTELLATION_BEIDOU;
                    gnssSvInfo->svid -= BEIDOU_SVID_OFFSET;
                } else if (gnssSvInfo->svid >= SBAS_SVID_MIN && gnssSvInfo->svid <= SBAS_SVID_MAX) {
                    gnssSvInfo->constellation = GNSS_CONSTELLATION_SBAS;
                    gnssSvInfo->svid += SBAS_SVID_ADD;
                } else if (gnssSvInfo->svid >= QZSS_SVID_MIN && gnssSvInfo->svid <= QZSS_SVID_MAX) {
                    gnssSvInfo->constellation = GNSS_CONSTELLATION_QZSS;
                } else if (gnssSvInfo->svid >= GALILEO_SVID_OFFSET &&
                           gnssSvInfo->svid <= GALILEO_SVID_OFFSET + GALILEOBEIDOU_SVID_COUNT) {
                    gnssSvInfo->constellation = GNSS_CONSTELLATION_GALILEO;
                    gnssSvInfo->svid -= GALILEO_SVID_OFFSET;
                } else {
                    ALOGW("Unknown constellation type with Svid = %d.", gnssSvInfo->svid);
                    gnssSvInfo->constellation = GNSS_CONSTELLATION_UNKNOWN;
                }
            } else if (gnssSystemID == "GL") {
                gnssSvInfo->constellation = GNSS_CONSTELLATION_GLONASS;
                if (gnssSvInfo->svid > GLONASS_SVID_OFFSET) {
                    gnssSvInfo->svid -= GLONASS_SVID_OFFSET;
                }
            } else if (gnssSystemID == "GA") {
                gnssSvInfo->constellation = GNSS_CONSTELLATION_GALILEO;
                if (gnssSvInfo->svid > GALILEO_SVID_OFFSET) {
                    gnssSvInfo->svid -= GALILEO_SVID_OFFSET;
                }
            } else if (gnssSystemID == "BD" || gnssSystemID == "GB") {
                gnssSvInfo->constellation = GNSS_CONSTELLATION_BEIDOU;
                if (gnssSvInfo->svid > BEIDOU_SVID_OFFSET) {
                    gnssSvInfo->svid -= BEIDOU_SVID_OFFSET;
                }
            }

            // Elevation (range 0-90)
            if (android::base::ParseDouble(parts[sentenceOffset + 5].c_str(), &dblValue)) {
                gnssSvInfo->elevation = dblValue;
            } else {
                gnssSvInfo->elevation = 0.0;
            }
            // Azimuth, (range 0-359)
            if (android::base::ParseDouble(parts[sentenceOffset + 6].c_str(), &dblValue)) {
                gnssSvInfo->azimuth = dblValue;
            } else {
                gnssSvInfo->azimuth = 0.0;
            }
            // Signal strength (C/N0, range 0-99), blank when not racking
            std::vector<std::string> cn0dbhz = android::base::Split(parts[sentenceOffset + 7], "*");
            if (cn0dbhz.size() > 0 && android::base::ParseDouble(cn0dbhz[0].c_str(), &dblValue)) {
                gnssSvInfo->c_n0_dbhz = dblValue;
            } else {
                gnssSvInfo->c_n0_dbhz = 0.0;
            }

            sGnssSvStatus.num_svs = sCurrentSvNumber + 1;
            sCurrentSvNumber = sCurrentSvNumber + 1;
        }
    } else {
        ALOGW("unknown sentence frameKey = %s, parts.size() = %lu [%s]", frameKey.c_str(),
              parts.size(), nmeaValue.c_str());
    }

    sMutex.lock();
    if (sNmeaReader->fix.gnssLocationFlags & GnssLocationFlags::HAS_LAT_LONG &&
        sNmeaReader->fix.gnssLocationFlags & GnssLocationFlags::HAS_HORIZONTAL_ACCURACY &&
        sNmeaReader->fix.gnssLocationFlags & GnssLocationFlags::HAS_ALTITUDE &&
        sNmeaReader->fix.gnssLocationFlags & GnssLocationFlags::HAS_SPEED &&
        sNmeaReader->gnss_location_cb && gnssRunStarted) {
        sNmeaReader->gnss_location_cb(&sNmeaReader->fix);
        nmeaDump();
        sNmeaReader->fix.gnssLocationFlags = 0;
    }

    if (isLastEntry && (sGSVReceiveComplete || sGnssSvStatus.num_svs == GNSS_MAX_SVS) &&
        sNmeaReader->gnss_location_cb && sGpsState->callbacks.gnss_sv_status_cb && gnssRunStarted) {
        sGpsState->callbacks.gnss_sv_status_cb(&sGnssSvStatus);
        nmeaSvStatusDump();

        sGSVReceiveComplete = false;
        sCurrentSvNumber = 0;
        memset(sGP_PRNList, 0, sizeof(sGP_PRNList));
        memset(sGL_PRNList, 0, sizeof(sGL_PRNList));
        memset(sGA_PRNList, 0, sizeof(sGA_PRNList));
        memset(sBD_PRNList, 0, sizeof(sBD_PRNList));
    }
    sMutex.unlock();
}

int GnssIpcp::nmeaUpdateTime(std::string utc_time) {
    int hour = 0, minute = 0;
    double seconds = 0.0;
    struct tm curTime;
    time_t fix_time;

    if (utc_time.length() < 6) return -1;

    if (sNmeaReader->utc_year < 0) {
        // no date yet, get current one
        time_t now = time(NULL);
        gmtime_r(&now, &curTime);
        if (curTime.tm_year >= 89) {
            curTime.tm_year = curTime.tm_year + 1900;
        } else {
            curTime.tm_year = curTime.tm_year + 2000;
        }
        sNmeaReader->utc_mon = curTime.tm_mon + 1;
        sNmeaReader->utc_day = curTime.tm_mday;
    }

    android::base::ParseInt(utc_time.substr(0, 2), &hour);
    android::base::ParseInt(utc_time.substr(2, 2), &minute);
    android::base::ParseDouble(utc_time.substr(4, 2).c_str(), &seconds);

    curTime.tm_hour = hour;
    curTime.tm_min = minute;
    curTime.tm_sec = (int)seconds;
    if (sNmeaReader->utc_year >= 89) {
        curTime.tm_year = sNmeaReader->utc_year - 1900;
    } else {
        curTime.tm_year = sNmeaReader->utc_year - 2000;
    }
    curTime.tm_mon = sNmeaReader->utc_mon - 1;
    curTime.tm_mday = sNmeaReader->utc_day;
    curTime.tm_isdst = -1;

    nmeaUpdateUtcDiff();

    // This is a little confusing, let's use an example:
    // Suppose now it's 1970-1-1 01:00 GMT, local time is 1970-1-1 00:00 GMT-1
    // Then the utc_diff is 3600.
    // The time string from GPS is 01:00:00, mktime assumes it's a local
    // time. So we are doing mktime for 1970-1-1 01:00 GMT-1. The result of
    // mktime is 7200 (1970-1-1 02:00 GMT) actually. To get the correct
    // timestamp, we have to subtract utc_diff here.
    fix_time = mktime(&curTime) - sNmeaReader->utc_diff;
    sNmeaReader->fix.timestamp = (long long)fix_time * 1000;
    return 0;
}

int GnssIpcp::nmeaUpdateDate(std::string date, std::string utc_time) {
    int day = -1, mon = -1, year = -1;

    if (date.length() != 6) {
        D("date not properly formatted: %s", date.c_str());
        return -1;
    }

    android::base::ParseInt(date.substr(0, 2), &day);
    android::base::ParseInt(date.substr(2, 2), &mon);
    android::base::ParseInt(date.substr(4, 2), &year);

    if (year >= 89) {
        year = year + 1900;
    } else {
        year = year + 2000;
    }

    if ((day | mon | year) < 0) {
        D("date not properly formatted: %s", date.c_str());
        return -1;
    }

    sNmeaReader->utc_year = year;
    sNmeaReader->utc_mon = mon;
    sNmeaReader->utc_day = day;

    return nmeaUpdateTime(utc_time);
}

void GnssIpcp::nmeaUpdateUtcDiff() {
    time_t now = time(NULL);
    struct tm tm_local;
    struct tm tm_utc;
    long time_local, time_utc;

    gmtime_r(&now, &tm_utc);
    localtime_r(&now, &tm_local);

    time_local = tm_local.tm_sec +
                 60 * (tm_local.tm_min +
                       60 * (tm_local.tm_hour + 24 * (tm_local.tm_yday + 365 * tm_local.tm_year)));

    time_utc =
        tm_utc.tm_sec +
        60 * (tm_utc.tm_min + 60 * (tm_utc.tm_hour + 24 * (tm_utc.tm_yday + 365 * tm_utc.tm_year)));

    sNmeaReader->utc_diff = time_utc - time_local;
}

double GnssIpcp::convertFromHHMM(std::string value) {
    double val = 0.0;
    android::base::ParseDouble(value.c_str(), &val);
    int degrees = (int)(floor(val) / 100);
    double minute = val - degrees * 100.;
    double dcoord = degrees + minute / 60.0;
    return dcoord;
}

int GnssIpcp::nmeaUpdateLatlong(std::string latitude, std::string latitudeHemi,
                                std::string longitude, std::string longitudeHemi) {
    double lat, lon;

    sNmeaReader->fix.gnssLocationFlags &= ~static_cast<uint16_t>(GnssLocationFlags::HAS_LAT_LONG);
    if (latitude.length() < 6) {
        D("latitude is too short: %s", latitude.c_str());
        return -1;
    }

    lat = convertFromHHMM(latitude);
    if (latitudeHemi == "S") lat = -lat;

    if (longitude.length() < 6) {
        D("longitude is too short: %s", longitude.c_str());
        return -1;
    }

    lon = convertFromHHMM(longitude);
    if (longitudeHemi == "W") lon = -lon;

    sNmeaReader->fix.gnssLocationFlags |= GnssLocationFlags::HAS_LAT_LONG;
    sNmeaReader->fix.latitudeDegrees = lat;
    sNmeaReader->fix.longitudeDegrees = lon;
    return 0;
}

int GnssIpcp::nmeaUpdateAltitude(std::string altitude, std::string aultitudeUnits) {
    sNmeaReader->fix.gnssLocationFlags &= ~static_cast<uint16_t>(GnssLocationFlags::HAS_ALTITUDE);
    if (altitude.empty()) return -1;

    double dblValue = 0.0;
    if (android::base::ParseDouble(altitude.c_str(), &dblValue)) {
        sNmeaReader->fix.gnssLocationFlags |= GnssLocationFlags::HAS_ALTITUDE;
        sNmeaReader->fix.altitudeMeters = dblValue;
    }
    return 0;
}

int GnssIpcp::nmeaUpdateBearing(std::string bearing) {
    sNmeaReader->fix.gnssLocationFlags &= ~static_cast<uint16_t>(GnssLocationFlags::HAS_BEARING);
    if (bearing.empty()) return -1;

    double dblValue = 0.0;

    if (android::base::ParseDouble(bearing.c_str(), &dblValue)) {
        sNmeaReader->fix.gnssLocationFlags |= GnssLocationFlags::HAS_BEARING;
        sNmeaReader->fix.bearingDegrees = dblValue;
    }
    return 0;
}

int GnssIpcp::nmeaUpdateSpeed(std::string speed) {
    sNmeaReader->fix.gnssLocationFlags &= ~static_cast<uint16_t>(GnssLocationFlags::HAS_SPEED);
    if (speed.empty()) return -1;

    double dblValue = 0.0;

    if (android::base::ParseDouble(speed.c_str(), &dblValue)) {
        sNmeaReader->fix.gnssLocationFlags |= GnssLocationFlags::HAS_SPEED;
        // convert Knots to m/s
        sNmeaReader->fix.speedMetersPerSec = dblValue * 0.514444;
    }
    return 0;
}

int GnssIpcp::nmeaUpdateHorizontalAccuracy(std::string accuracy) {
    sNmeaReader->fix.gnssLocationFlags &= ~static_cast<uint16_t>(GnssLocationFlags::HAS_HORIZONTAL_ACCURACY);
    if (accuracy.empty()) return -1;

    double dblValue = 0.5;

    if (android::base::ParseDouble(accuracy.c_str(), &dblValue)) {
        if (dblValue >= 0.0f) {
            sNmeaReader->fix.gnssLocationFlags |= GnssLocationFlags::HAS_HORIZONTAL_ACCURACY;
            sNmeaReader->fix.horizontalAccuracyMeters = dblValue;
        }
    }

    return 0;
}

int GnssIpcp::nmeaUpdateVerticalAccuracy(std::string accuracy) {
    sNmeaReader->fix.gnssLocationFlags &= ~static_cast<uint16_t>(GnssLocationFlags::HAS_VERTICAL_ACCURACY);
    if (accuracy.empty()) return -1;

    double dblValue = 0.5;

    if (android::base::ParseDouble(accuracy.c_str(), &dblValue)) {
        if (dblValue >= 0.0f) {
            sNmeaReader->fix.gnssLocationFlags |= GnssLocationFlags::HAS_VERTICAL_ACCURACY;
            sNmeaReader->fix.verticalAccuracyMeters = dblValue;
        }
    }

    return 0;
}

void GnssIpcp::nmeaDump() {
    D("utc_year = %d, utc_mon = %d, utc_day = %d, "
       "utc_diff = %d, flags = %X, "
       "latitude = %f, longitude = %f, "
       "altitude = %f, speed = %f, "
       "bearing = %f, horizontalAccuracyMeters = %f, "
       "verticalAccuracyMeters = %f, timestamp = %ld",
       sNmeaReader->utc_year, sNmeaReader->utc_mon, sNmeaReader->utc_day,
       sNmeaReader->utc_diff, sNmeaReader->fix.gnssLocationFlags,
       sNmeaReader->fix.latitudeDegrees, sNmeaReader->fix.longitudeDegrees,
       sNmeaReader->fix.altitudeMeters, sNmeaReader->fix.speedMetersPerSec,
       sNmeaReader->fix.bearingDegrees, sNmeaReader->fix.horizontalAccuracyMeters,
       sNmeaReader->fix.verticalAccuracyMeters, sNmeaReader->fix.timestamp);
}

void GnssIpcp::nmeaSvStatusDump() {
    D("sGnssSvStatus.num_svs = %d", sGnssSvStatus.num_svs);
    for (int i = 0; i < sGnssSvStatus.num_svs; i++) {
        D("svid:%d, constellation:%d, elevation:%f, azimuth:%f c_n0_dbhz:%f, flags:%d",
          sGnssSvStatus.gnss_sv_list[i].svid, sGnssSvStatus.gnss_sv_list[i].constellation,
          sGnssSvStatus.gnss_sv_list[i].elevation, sGnssSvStatus.gnss_sv_list[i].azimuth,
          sGnssSvStatus.gnss_sv_list[i].c_n0_dbhz, sGnssSvStatus.gnss_sv_list[i].flags);
    }
}

}  // namespace implementation
}  // namespace V1_0
}  // namespace gnss
}  // namespace xma
}  // namespace ecarx
}  // namespace vendor
